#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <getopt.h>
#include <errno.h>
#include <stdarg.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <netinet/tcp.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <ifaddrs.h>
#include <arpa/inet.h>
#include <linux/if.h>
#include <linux/sockios.h>
#include <linux/ethtool.h>
#include <linux/if_packet.h>

#define MAX_FD 100

char *interface, *script, *port;
struct epoll_event events[MAX_FD];
int epoll_fd;
char ifn[MAX_FD][IFNAMSIZ];

int socket_epoll_add(int fd){
	struct epoll_event ev;
	ev.data.fd = fd;
	ev.events = EPOLLIN;
	if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0){
		perror("epoll_ctl");
		return -1;
	}
	return 0;
}

int socket_epoll_del(int fd){
	struct epoll_event ev;
	ev.data.fd = fd;
	ev.events = EPOLLIN;
	if(epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, &ev) < 0){
		perror("epoll_ctl");
		return -1;
	}
	return 0;
}

int socket_epoll_wait(int timeout){
	int nfds = epoll_wait(epoll_fd, events, MAX_FD, timeout);
	if(nfds < 0){
		perror("epoll_wait");
		return -1;
	}
	return nfds;
}

int socket_init(char *iface){
	int fd = socket(AF_INET, SOCK_DGRAM, 0);
	if(fd < 0){
		perror("socket");	
		return -1;
	}

	//Reuseaddr
	int opt = 1;
	if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0){
		perror("setsockopt");
		return -1;
	}

	//Bind iface
	struct ifreq ifr;
	bzero(&ifr,sizeof(ifr));
	strncpy(ifr.ifr_name, iface, IFNAMSIZ);
	if(setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr)) != 0){
		perror("setsockopt");
		return -1;
	}

	//Set Broadcast
	opt = 1;
	if(setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &opt, sizeof(opt)) < 0){
		perror("setsockopt");
		return -1;
	}

	//setnonblocking
	if(fcntl(fd, F_SETFL, fcntl(fd, F_GETFD, 0)|O_NONBLOCK) < 0){
		perror("fcntl");
		return -1;
	}
	
	//Bind addr
	struct sockaddr_in sa;
	sa.sin_family = AF_INET;
	sa.sin_port = htons(atoi(port));
	sa.sin_addr.s_addr = htonl(INADDR_ANY);
	if(bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0){
		perror("bind");
		return -1;
	}

	return fd;
}

int shell(char *sbuf, int sbuf_size, char *rbuf, int rbuf_size){
	char *c = malloc(sbuf_size*2);
	bzero(c, sbuf_size*2);
	strcat(c, script);
	strcat(c, " ");
	strcat(c, rbuf);
	FILE *fp = popen(c, "r");
	if(fp == NULL){
		perror("popen");
		return -1;
	}
	int t = fread(sbuf, 1, sbuf_size, fp);
	pclose(fp);
	free(c);
	return t;
}

int respond(int fd, char *rbuf, int rbuf_size, struct sockaddr_in *sa, socklen_t sa_len){
	//fprintf(stdout, "recv: %s\n", rbuf);
	char sbuf[8192];
	//set environment
	setenv("LOCAL_IFACE", ifn[fd], sizeof(ifn[fd]));
	setenv("REMOTE_ADDR", inet_ntoa(sa->sin_addr), 1);
	char port[128];
	sprintf(port, "%d", ntohs(sa->sin_port));
	setenv("REMOTE_PORT", port, 1);
	//shell
	int t = shell(sbuf, sizeof(sbuf), rbuf, rbuf_size);
	//unset environment
	unsetenv("LOCAL_IFACE");
	unsetenv("REMOTE_ADDR");
	unsetenv("REMOTE_PORT");
	if(t <= 0) return -1;
	//fprintf(stdout, "send: %s\n", sbuf);
	t = sendto(fd, sbuf, t, 0, (struct sockaddr *)sa, sa_len);
	if(t < 0){
		perror("sendto");
		return -1;
	}
	return 0;
}

int handle_packet(int fd){
	char rbuf[8192];
	bzero(rbuf, sizeof(rbuf));
	struct sockaddr_in sa;
	socklen_t sa_len = sizeof(sa); 
	int ret = recvfrom(fd, rbuf, sizeof(rbuf), 0, (struct sockaddr *)&sa, &sa_len);
	if(ret < 0){
		perror("recvfrom");
		close(fd);
		return -1;
	}
	//has data comming (include zero size packet)
	if(ret >= 0){
		//printf("%d %s\n", fd, ifn[fd]);	
		respond(fd, rbuf, ret, &sa, sa_len);
	}
	return 0;
}

int start_server(){
	//create epoll fd
	epoll_fd = epoll_create(MAX_FD);
	if(epoll_fd < 0){
		perror("epoll_create");
		return -1;
	}

	//forecho iface
	struct ifaddrs *ifa,*iflist;
	if(getifaddrs(&iflist) < 0) return 0;
	for(ifa=iflist;ifa!=NULL;ifa=ifa->ifa_next){
		if(ifa->ifa_addr->sa_family == AF_INET){
			printf("iface: %s\n",ifa->ifa_name);
			//init socket if has iface
			int fd = socket_init(ifa->ifa_name);
			if(fd < 0 || socket_epoll_add(fd) < 0) continue;
			strncpy(ifn[fd], ifa->ifa_name, IFNAMSIZ);
		}
	}
	freeifaddrs(iflist);

	printf("Listing...\n");

	while(1){
		int nfds = socket_epoll_wait(1000);
		if(nfds > 0){
			int i;
			for(i=0;i<nfds;i++){
				handle_packet(events[i].data.fd);
			}
		}else{
			//timeout
			continue;
		}
	}
}

int usage(const char *progname)
{
	fprintf(stderr, "Shell 1.1 By Tuber\n\n"
			"Usage: %s <options>\n"
			"\n"
			"Options:\n"
			"\t-s <script>\n"
			"\t-p <port>\n"
			"\t-b <background>\n"
			"\n",
		progname);
	return(1);
}

int main(int argc, char *argv[]){
	int ch, background = 0;

	while ((ch = getopt(argc, argv, "bp:s:")) != -1) {
		switch(ch) {
			case 's':
				script = optarg;
				break;
			case 'p':
				port = optarg;
				break;
			case 'b':
				background = 1;
				break;
			default:
				usage(argv[0]);
				return(1);
		}
	}
	
	if(script == NULL || port == NULL){
		usage(argv[0]);
		return(1);
	}
	
	pid_t fpid = 0;
	if(background){
		fpid = fork();
	}

	if (fpid < 0){
		fprintf(stderr, "fork: %s\n", strerror(errno));
		return(-1);
	}else if(fpid == 0){
		start_server();
	}

	return 0;
}

